package main

import (
	"code.google.com/p/go.net/publicsuffix"
	"crypto/sha256"
	"encoding/hex"
	"encoding/json"

	_ "errors"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/http/cookiejar"
	"os"
	_ "strconv"
	"strings"
	"time"
)

const (
	BrowserPrefix = "[B_ser]"

	RelayInfo   = "/info?vt=json"
	RelayStatus = "/status?vt=json"

	BrowserReconnectServerInterval = 60 //second

)

var (
	HttpReqHome         = "http://" + CatfishServer + "/"
	HttpReqLogin        = "http://" + CatfishServer + "/login?vt=json"
	HttpReq_GetDeviceID = "http://" + CatfishServer + "/device?mac=%s&vt=json"
	HttpReq_Status      = "http://" + CatfishServer + "/device/%s" + RelayStatus
	HttpReq_Info        = "http://" + CatfishServer + "/device/%s" + RelayInfo
)

func UrlReSet() {
	HttpReqHome = "http://" + CatfishServer + "/"
	HttpReqLogin = "http://" + CatfishServer + "/login?vt=json"
	HttpReq_GetDeviceID = "http://" + CatfishServer + "/device?mac=%s&vt=json"
	HttpReq_Status = "http://" + CatfishServer + "/device/%s" + RelayStatus
	HttpReq_Info = "http://" + CatfishServer + "/device/%s" + RelayInfo
}

type PesudoBrowser struct {
	User      string
	Pass      string
	DeviceMac string
	DeviceId  string

	Cookie string
	Client *http.Client

	// Verify System
	MsgCache *AssertCache
	Seq      int
	Quit     bool

	RequestFailCounter int
}

type FishResponse struct {
	Code int
	Msg  string
	Data string
}

type EcareResponse struct {
	TestSeq int
}

func (this *PesudoBrowser) JsonGet(url string) (*FishResponse, error) {
	fishRsp := FishResponse{}
	req, err := http.NewRequest("GET", url, nil)
	if err != nil {
		return nil, err
	} else {
		req.Header.Set("Cookie", this.Cookie)
		response, err := this.Client.Do(req)
		if err != nil {
			return nil, err
		} else {
			data, err := ioutil.ReadAll(response.Body)
			// fmt.Println("jsondata>>", string(data))
			defer response.Body.Close()
			if err != nil {
				return nil, err
			} else {
				json.Unmarshal(data, &fishRsp)
				return &fishRsp, nil
			}
		}
	}
}

// Login And Follow Redirect
func (this *PesudoBrowser) Login() bool {

	// setup cookie
	options := cookiejar.Options{
		PublicSuffixList: publicsuffix.List,
	}
	jar, err := cookiejar.New(&options)
	if err != nil {
		BrowserLoger.Debug("%s %s new cookiejar Fail. err:%s", BrowserPrefix, this.DeviceMac, err.Error())
		return false
	}
	this.Client = &http.Client{Jar: jar}

	// generate sha256 password
	hash := sha256.New()
	hash.Write([]byte(this.Pass))
	md := hash.Sum(nil)
	mdStr := hex.EncodeToString(md)
	postData := "email=" + this.User + "&password=" + mdStr

	// Login Server
	resp, err := this.Client.Post(HttpReqLogin, "application/x-www-form-urlencoded", strings.NewReader(postData))
	if err != nil {
		BrowserLoger.Debug("%s %s Post Fail %s err:%s", BrowserPrefix, this.DeviceMac, postData, err.Error())
		return false
	}

	// Save Cookie.
	this.Cookie = resp.Header.Get("Set-Cookie")
	data, err := this.JsonGet(fmt.Sprintf(HttpReq_GetDeviceID, this.DeviceMac))
	if err == nil {
		this.DeviceId = data.Data
		BrowserLoger.Debug("%s %s GetDeviceID: %s", BrowserPrefix, this.DeviceMac, this.DeviceId)
		return true
	} else {
		BrowserLoger.Debug("%s %s GetDeviceID Fail. err:%s", BrowserPrefix, this.DeviceMac, err.Error())
		return false
	}
}

func (this *PesudoBrowser) SetMsgCache(cache *AssertCache) {
	this.MsgCache = cache
}

func (this *PesudoBrowser) Work() {
	current_times := 1

	for {
		if this.Login() {
			for {
				url := ""
				msg := ""
				switch current_times % 2 {
				case 0:
					msg = RelayInfo
					url = fmt.Sprintf(HttpReq_Info, this.DeviceId)
				case 1:
					msg = RelayStatus
					url = fmt.Sprintf(HttpReq_Status, this.DeviceId)
				}
				if this.MsgCache != nil {
					// Override msg & url
					msg = msg + fmt.Sprintf("&seq=%d", this.Seq)
					url = url + fmt.Sprintf("&seq=%d", this.Seq)

					this.MsgCache.SendMsg(fmt.Sprintf("b-seq-%d", this.Seq), msg)
					this.Seq++
				}
				stime := fmt.Sprintf("%d", current_times)
				BrowserLoger.Info("%s %s [Send_] [Times:%s] %s", BrowserPrefix, this.DeviceMac, stime, url)

				data, err := this.JsonGet(url)
				if err == nil {
					if data.Code == 0 {
						if len(data.Data) <= 1 {
							byteD, _ := json.Marshal(data)
							BrowserLoger.Warn("%s %s [Recv_] [Times:%s] [Less]%s", BrowserPrefix, this.DeviceMac, stime, string(byteD))
							goto relogin
						} else {
							BrowserLoger.Info("%s %s [Recv_] [Times:%s] %s", BrowserPrefix, this.DeviceMac, stime, data.Data)
							if this.MsgCache != nil {
								rsp := EcareResponse{}
								json.Unmarshal([]byte(data.Data), &rsp)

								this.MsgCache.ReceiveMsg(fmt.Sprintf("e-seq-%d", rsp.TestSeq), data.Data)
							}
						}
					} else {
						byteD, _ := json.Marshal(data)
						BrowserLoger.Warn("%s %s [Recv_] [Times:%s] [Code:%d]%s", BrowserPrefix, this.DeviceMac, stime, data.Code, string(byteD))
						goto relogin
					}
				} else {
					// dial tcp 218.244.150.208:80: connection refused
					BrowserLoger.Warn("%s %s [Recv_] [Times:%s] err:%s", BrowserPrefix, this.DeviceMac, stime, err.Error())
					goto relogin
				}

				// browser
				<-time.After(time.Duration(BrowserInterval) * time.Second)
				current_times++

				// BrowserTimes == 0 means for ever.

				if BrowserTimes != 0 && current_times > BrowserTimes {
					goto done
				}
			}
		} else {
			BrowserLoger.Warn("%s %s [Login] Fail! retry in %d(s)\n", BrowserPrefix, this.DeviceMac, BrowserReconnectServerInterval)
			// retry after 60 second
			<-time.After(BrowserReconnectServerInterval * time.Second)
		}

	relogin:
		BrowserLoger.Debug("%s %s [Re-Login] ", BrowserPrefix, this.DeviceMac)

	}
done:
	BrowserLoger.Debug("%s %s [Recv_] Work Done", BrowserPrefix, this.DeviceMac)
}

func (this *PesudoBrowser) Exit() {
	this.Quit = true
}

func JsonPrint(obj interface{}) {
	b, err := json.Marshal(obj)
	if err != nil {
		fmt.Println("error:", err)
		return
	}
	// fmt.Println(b)
	os.Stdout.Write(b)
	fmt.Println("")
}

func PrintAllResponse(rsp *http.Response) {
	fmt.Println(">> resp status>>", rsp.StatusCode, rsp.Status)
	JsonPrint(rsp.Header)
	data, err := ioutil.ReadAll(rsp.Body)
	if err == nil {
		fmt.Println(string(data))
	}
	rsp.Body.Close()
}
